using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Security;
using System.Drawing;

namespace InfiniTec.Security
{
    /// <summary>
    /// Implements a common dialog that asks the user to provide a username and password
    /// combination, by calling the CredUIPromptForCredentials() Win32 function.
    /// </summary>
    public class QueryCredentialDialog: CommonDialog
    {
        private string _CaptionText = "";

        private string _Username = "";

        private string _TargetName = "";

        /// <summary>
        /// The targetname for the credential.
        /// </summary>
        public string TargetName
        {
            get { return _TargetName; }
            set { _TargetName = value; }
        }

        /// <summary>
        /// Marks the credentials stored with the current targetname either as valid or as invalid.
        /// </summary>
        /// <param name="valid">True, if the entered credentials are valid, false otherwise.</param>
        public void ConfirmCredentials(bool valid)
        {
            NativeMethods.CredUIConfirmCredentials(TargetName, valid);
        }
        
        private QueryCredentialOptions _Options = QueryCredentialOptions.GenericCredentials |
            QueryCredentialOptions.ShowSaveCheckBox |
            QueryCredentialOptions.AlwaysShowUI |
            QueryCredentialOptions.ExpectConfirmation;

        /// <summary>
        /// The <see cref="QueryCredentialOptions"/> used when displaying the dialog.
        /// </summary>
        public QueryCredentialOptions Options
        {
            get { return _Options; }
            set { _Options = value; }
        }

        /// <summary>
        /// Gets or sets the username. If set before the <see cref="CommonDialog.ShowDialog()"/> method is called,
        /// the username field of the dialog will be populated with this value. If the
        /// <see cref="CommonDialog.ShowDialog()"/> returns <see cref="DialogResult.OK"/>, this property
        /// contains the username specified by the user.
        /// </summary>
        public string Username
        {
            get { return _Username; }
            set { _Username = value; }
        }

        private SecureString _Password;

        /// <summary>
        /// A <see cref="SecureString"/> containing the password.
        /// </summary>
        public SecureString Password
        {
            get { return _Password; }
            set { _Password = value; }
        }

        /// <summary>
        ///  A string containing the title for the dialog box
        /// </summary>
        public string CaptionText
        {
            get { return _CaptionText; }
            set { _CaptionText = value; }
        }

        private Bitmap _Image;

        /// <summary>
        /// Bitmap to display in the dialog box. If this member is null, a default 
        /// bitmap is used. The bitmap size is limited to 320x60 pixels. 
        /// </summary>
        public Bitmap Image
        {
            get { return _Image; }
            set { _Image = value; }
        }

        private string _MessageText;

        /// <summary>
        /// A string containing a brief message to display in the dialog box.
        /// </summary>
        public string MessageText
        {
            get { return _MessageText; }
            set { _MessageText = value; }
        }

        private bool _SavePassword;

        /// <summary>
        /// If true, the "Save Password" checkbox is checked, false otherwise.
        /// </summary>
        public bool SavePassword
        {
            get { return _SavePassword; }
            set { _SavePassword = value; }
        }


        /// <summary>
        /// Does nothing.
        /// </summary>
        public override void Reset()
        {
        }

        /// <summary>
        /// Displays a dialog that requests a username/password combination from the user.
        /// </summary>
        /// <param name="hwndOwner"></param>
        /// <returns>True, if a username/password combination was provided. False, otherwise</returns>
        protected override bool RunDialog(IntPtr hwndOwner)
        {
        	CREDUI_INFO info = new CREDUI_INFO();
            info.hwndParent = hwndOwner;
            info.pszCaptionText = CaptionText;
            info.pszMessageText = MessageText;
            if (Image != null) info.hbmBanner = Image.GetHbitmap();
            info.cbSize = Marshal.SizeOf(info);
            StringBuilder username = new StringBuilder(256+1);
            username.Append(Username);

            char[] password = new char[256 + 1];
            bool save = SavePassword;
            unsafe
            {
                fixed (char* buffer = password)
                {
                    try
                    {
                        Array.Clear(password, 0, password.Length);

                        QueryCredentialError result = UnsafeNativeMethods.CredUIPromptForCredentials(ref info, TargetName, IntPtr.Zero, 0, username, username.Capacity, buffer, 256 * sizeof(char), ref save, Options);
                        if (result == QueryCredentialError.Cancelled) return false;

                        if (result != QueryCredentialError.None)
                            throw new QueryCredentialDialogException(result, "An exception querying the user credentials.");

                        _SavePassword = save;

                        int length = 0;
                        while (length < password.Length && password[length] != 0) length++;

                        _Username = username.ToString();
                        _Password = new SecureString(buffer, length);
                    }
                    finally
                    {
                        Array.Clear(password, 0, password.Length);
                    }

                    return true;
                }
            }
        }
    }
}
